﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/16 16:32:20 
* clrversion :4.0.30319.18444
******************************************************/

using Soul.DataAccess.Common;
using Soul.DataAccess.Common.ORM;
using Soul.DataAccess.Linq;
using Soul.DataAccess.Linq.Data;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Soul.DataAccess.Procedure
{
    /// <summary>
    /// 访问存储过程的辅助类,单例模式
    /// </summary>
    [Obsolete("已经放弃，不推荐使用")]
    public class ProcedureHelper
    {
        private static Dictionary<Type, object> cache = new Dictionary<Type, object>();
        private static Dictionary<string, ProcedureParametersDic> procedureParametersCache = new Dictionary<string, ProcedureParametersDic>();
        private static readonly string procedureParameters_get = @"select syscolumns.name as parameterName, systypes.name as typeName from syscolumns left join systypes on syscolumns.xtype=systypes.xtype  where id =(select id from sysobjects where name= @procedureName)";

        private static Lazy<ProcedureHelper> _instance = new Lazy<ProcedureHelper>(() =>
        {
            return new ProcedureHelper();
        }, true);
        /// <summary>
        /// 返回ProcedureHelper
        /// </summary>
        public static ProcedureHelper Instance { get { return _instance.Value; } }
        private ProcedureHelper()
        {
            excuteReaderMethodInfo = typeof(ProcedureHelper).GetMethod("ExecuteReader", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
            excuteResultMethodInfo = typeof(ProcedureHelper).GetMethod("ExecuteResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
            excuteNonQueryMethodInfo = typeof(ProcedureHelper).GetMethod("ExecuteNonQuery", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            testMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
            dicConstructorInfo = typeof(Dictionary<string, object>).GetConstructor(new Type[0]);
            dicAddMethodInfo = typeof(Dictionary<string, object>).GetMethod("Add");
        }

        private MethodInfo excuteReaderMethodInfo;
        private MethodInfo excuteResultMethodInfo;
        private MethodInfo excuteNonQueryMethodInfo;
        private MethodInfo testMethod;
        private ConstructorInfo dicConstructorInfo;
        private MethodInfo dicAddMethodInfo;

        /// <summary>
        /// 根据存储过程数据类装载存储过程的调用方法
        /// </summary>
        /// <typeparam name="TProcData">存储过程数据类型</typeparam>
        /// <returns>装载后的可调用对象</returns>
        public TProcData BuildProcedureData<TProcData>() where TProcData : class
        {
            var type = typeof(TProcData);
            if (cache.ContainsKey(type)) return cache[type] as TProcData;
            var methods = type.GetMethods();
            AssemblyBuilder asmbuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(type.Assembly.FullName), AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder modulBuilder = asmbuilder.DefineDynamicModule("Proc");
            TypeBuilder typeBuilder = modulBuilder.DefineType(string.Format("Wrap_{0}", type.Name), TypeAttributes.Class, type);
            foreach (var method in methods)
            {
                if (!method.IsAbstract) continue;
                var procAttribute = method.GetCustomAttributes(typeof(ProcedureAttribute), true).FirstOrDefault() as ProcedureAttribute;
                if (procAttribute != null) this.BuildMethod(method, typeBuilder, procAttribute);
            }
            var obj = Activator.CreateInstance(typeBuilder.CreateType());
            cache[type] = obj;
            return obj as TProcData;
        }

        private void BuildMethod(MethodInfo method, TypeBuilder typeBuilder, ProcedureAttribute procAttribute)
        {
            var parameters = method.GetParameters();
            Type returnType = method.ReturnType;// method.ReturnType.GetGenericArguments()[0];
            MethodInfo methodCall = null;
            if (returnType == typeof(void)) throw new NotSupportedException("没有返回void的存储过程");
            else if (returnType.IsGenericType && procAttribute.ProcedureReturnType == ProcedureReturnType.DbSet)
            {
                returnType = method.ReturnType.GetGenericArguments()[0];
                methodCall = this.excuteReaderMethodInfo.MakeGenericMethod(returnType);
            }
            else if (returnType == typeof(bool) && procAttribute.ProcedureReturnType == ProcedureReturnType.NoData)
            {
                returnType = typeof(bool);
                methodCall = this.excuteNonQueryMethodInfo;
            }
            else if (procAttribute.ProcedureReturnType == ProcedureReturnType.Single && !returnType.IsArray)
            {
                returnType = method.ReturnType;
                methodCall = this.excuteResultMethodInfo.MakeGenericMethod(returnType);
            }
            else
            {
                throw new ArgumentException("存储过程标签异常");
            }

            var methodBuild = typeBuilder.DefineMethod(method.Name,
                MethodAttributes.Virtual | MethodAttributes.Public,
                method.ReturnType,
                parameters.Select(x => x.ParameterType).ToArray());

            var il = methodBuild.GetILGenerator();
            var endLable = il.DefineLabel();

            il.Emit(OpCodes.Newobj, dicConstructorInfo);
            il.Emit(OpCodes.Stloc_0);
            for (int i = 0; i < parameters.Count(); i++)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, parameters[i].Name);
                il.Emit(OpCodes.Ldarg, i + 1);
                if (parameters[i].ParameterType.IsValueType) il.Emit(OpCodes.Box, parameters[i].ParameterType);
                il.Emit(OpCodes.Callvirt, dicAddMethodInfo);
                //il.Emit(OpCodes.Stloc_0);
            }
            il.BeginExceptionBlock();
            il.Emit(OpCodes.Ldstr, procAttribute.ProcedureName);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, methodCall);
            il.Emit(OpCodes.Stloc_1);
            il.Emit(OpCodes.Ldloc_1);
            if (returnType.IsValueType) il.Emit(OpCodes.Box, returnType);
            il.Emit(OpCodes.Br, endLable);
            il.BeginCatchBlock(typeof(Exception));
            il.Emit(OpCodes.Throw);
            il.EndExceptionBlock();
            il.MarkLabel(endLable);
            il.Emit(OpCodes.Ret);
        }

        /// <summary>
        /// 执行存储过程，并返回结果
        /// </summary>
        /// <typeparam name="TEntity">ORM对象</typeparam>
        /// <param name="procedureName">存储过程名</param>
        /// <param name="parameters">存储过程参数</param>
        /// <returns>集合</returns>
        public static IEnumerable<TEntity> ExecuteReader<TEntity>(string procedureName, IDictionary<string, object> parameters)
        {
            var sqlParameters = MatchSqlParameters(procedureName, parameters);
            return new DataReader<TEntity>(new TranslateResult(procedureName, sqlParameters),
                Config.Instance.GetProviderByKey()
                CommandType.StoredProcedure);
        }

        /// <summary>
        /// 执行存储过程，并返回结果
        /// </summary>
        /// <typeparam name="TResult">ORM对象</typeparam>
        /// <param name="procedureName">存储过程名</param>
        /// <param name="parameters">存储过程参数</param>
        /// <returns>结果</returns>
        public static TResult ExecuteResult<TResult>(string procedureName, IDictionary<string, object> parameters)
        {
            var sqlParameters = MatchSqlParameters(procedureName, parameters);
            return DataResult.GetResult<TResult>(new TranslateResult(procedureName, sqlParameters), CommandType.StoredProcedure);
        }

        /// <summary>
        /// 执行存储过程，并返回结果
        /// </summary>
        /// <param name="procedureName">存储过程名</param>
        /// <param name="parameters">存储过程参数</param>
        /// <returns>结果</returns>
        public static bool ExecuteNonQuery(string procedureName, IDictionary<string, object> parameters)
        {
            var sqlParameters = MatchSqlParameters(procedureName, parameters);
            return DataResult.ExecuteNonQuery(new TranslateResult(procedureName, sqlParameters), CommandType.StoredProcedure);
        }

        private static ProcedureParametersDic GetProcedureParameters(string procedureName)
        {
            if (!procedureParametersCache.ContainsKey(procedureName))
            {
                var cache = new ProcedureParametersDic();
                var parameters = GetProcedureParametersByDataBase(procedureName);
                foreach (var parameter in parameters)
                {
                    cache.Add(parameter.ParameterName, parameter.ParameterType);
                }
                procedureParametersCache[procedureName] = cache;
            }
            return procedureParametersCache[procedureName];
        }

        private static IEnumerable<ProcedureParameter> GetProcedureParametersByDataBase(string procedureName)
        {
            List<SqlParameter> parameters = new List<SqlParameter>() { new SqlParameter("procedureName", procedureName) };
            return new DbSet<ProcedureParameter>().ExcuteSql(procedureParameters_get, parameters);
        }

        private static IDictionary<string, object> ChangeInPutParameters(IDictionary<string, object> dic)
        {
            var changeList = new Dictionary<string, object>();
            foreach (var item in dic)
            {
                var type = item.Value.GetType();
                var code = Type.GetTypeCode(type);
                if (code == TypeCode.Object && type != typeof(Guid) && !type.IsArray)
                {
                    changeList.Add(item.Key, item.Value);
                }
            }

            if (changeList.Count == 0) return dic;

            foreach (var item in changeList)
            {
                dic.Remove(item.Key);
                var properties = DynamicAssignment.Instance.GetDictionary(item.Value.GetType());
                foreach (var property in properties)
                {
                    dic.Add(property.Key, property.Value.GetValue(item.Value));
                }
            }

            return ChangeInPutParameters(dic);
        }

        private static IEnumerable<SqlParameter> MatchSqlParameters(string procedureName, IDictionary<string, object> dic)
        {
            List<SqlParameter> parameters = new List<SqlParameter>();

            var newDic = ChangeInPutParameters(dic);

            var procedureDic = GetProcedureParameters(procedureName);
            foreach (var item in procedureDic)
            {
                if (!newDic.ContainsKey(item.Key)) throw new ArgumentNullException(string.Format("存储过程找不到参数:{0}", item.Key));
                object value = newDic[item.Key];
                if (item.Value == typeof(string) && value.GetType().IsArray)
                {
                    StringBuilder builder = new StringBuilder();
                    foreach (var valueItem in value as IEnumerable)
                    {
                        if (builder.Length > 0) builder.Append(',');
                        builder.Append(valueItem.ToString());
                    }
                    value = builder.ToString();
                }
                parameters.Add(new SqlParameter(item.Key, value));
            }

            return parameters;
        }

        private class ProcedureParametersDic : Dictionary<string, Type> { }
        private class ProcedureParameter
        {
            private string parameterName;
            public string ParameterName
            {
                get { return parameterName; }
                set { parameterName = value.Replace("@", ""); }
            }
            public string TypeName { get; set; }

            public Type ParameterType { get { return GetTypeByTypeName(this.TypeName); } set { } }

            private static Type GetTypeByTypeName(string typeName)
            {
                if (typeName == "uniqueidentifier") return typeof(Guid);
                if (typeName == "varchar") return typeof(string);
                if (typeName == "int") return typeof(int);
                return Type.GetType(typeName, true);
            }
        }
    }
}
